package chessmate;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Random;

public class State {

	public char [][] board;
	public int moveNum;
	public char onMove;
	Random ran = new Random();
	public Move m0;						//best Move from Negamax
	public String bestmove;
	//new
	public static long time;
	public static final long limit = 6000;
	public int dcount = 0;
	
	public State(){
		/**
		 * initializes the board
		 */
		board = new char[][]{
			{'k', 'q', 'b', 'n', 'r'},
			{'p', 'p', 'p', 'p', 'p'},
			{'.', '.', '.', '.', '.'}, 
			{'.', '.', '.', '.', '.'}, 
			{'P', 'P', 'P', 'P', 'P'}, 
			{'R', 'N', 'B', 'Q', 'K'}
		};
		
		onMove = 'w';
		moveNum = 1;
	}//end constructor
	
	public State(State state){
		this.onMove = state.onMove;
		this.moveNum = state.moveNum;
		this.board = new char[6][5];
		for(int i = 0; i < board.length; i++){
			for(int j = 0; j < board[i].length; j++){
				this.board[i][j] = state.board[i][j];
			}//end for j
		}//end for i
		this.ran = new Random();
	}//end constructor
	
	public State(String str){
		String [] stringToSplit = str.split("\n");
		board = new char[6][5];
		
		int count = 0;
		
		this.onMove = stringToSplit[count++].charAt(0);
		this.moveNum = Integer.parseInt(String.valueOf(stringToSplit[count++].charAt(0)));
		
		for(int i = 0; i < board.length; i++){
			for(int j = 0; j < board[i].length; j++){
				board[i][j] = stringToSplit[count++].charAt(0);
			}//end for j
		}//end for i
	}//end constructor
	
	public State(InputStream is){
		String line = new String();
		board = new char[6][5];
		
		try{
			int count = 0;
			String[] stringToSplit;
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			
			line = br.readLine();
			stringToSplit = line.split("\n");
			
			this.onMove = stringToSplit[count++].charAt(0);
			this.moveNum = stringToSplit[count++].charAt(0);
			
			for(int i = 0; i < board.length; i++){
				for(int j = 0; j < board[i].length; j++){
					board[i][j] = stringToSplit[count++].charAt(0);
				}//end for j
			}//end for i

			br.close();
			isr.close();
			is.close();
			
		}catch(Exception e){
			System.out.println(e.getMessage());
		}//end try catch
	}//end 
	
	public void print(OutputStream out){
		try{
			OutputStreamWriter osw = new OutputStreamWriter(out);
			BufferedWriter bw = new BufferedWriter(osw);
			
			bw.write(this.toString());
			
			bw.close();
			osw.close();
		}catch(Exception e){
			System.out.println(e.getMessage());
		}//end try catch
	}//end method
	
	public String toString(){
		String str;
		StringBuilder strbuild = new StringBuilder();
		strbuild.append(this.onMove);
		strbuild.append("\n");
		strbuild.append(this.moveNum);
		strbuild.append("\n");
		
		for(int i = 0; i < board.length; i++){
			for(int j = 0; j < board[i].length; j++){
				strbuild.append(board[i][j]);
				strbuild.append("\n");
			}//end for j
		}//end for i
		
		str = strbuild.toString();
		return str;
	}//End method

	/**
	 * moves the piece to the new location on the board
	 * 
	 * @param oldPosition
	 * @param newPosition
	 */
	public char move(Square oldPosition, Square newPosition){
		char outcome = 'a';	//outcome of the game -- 'a' is default(means nothing)
		
		if(getBoardWithCoordinates(newPosition.col, newPosition.row) == 'k'){			//white is the winner
			outcome = 'w';
		}else if(getBoardWithCoordinates(newPosition.col, newPosition.row) == 'K'){		//black is the winner
			outcome = 'b';
		}//end if else
		
		if(this.moveNum == 40){															//draw
			outcome = '-';
		}//end if
		
		board[newPosition.row][newPosition.col] = board[oldPosition.row][oldPosition.col];
		board[oldPosition.row][oldPosition.col] = '.';
		
		if(newPosition.row == 1){				//Black Pawn reaches edge -- turns into new Black Queen
			if(getBoardWithCoordinates(newPosition.col, newPosition.row) == 'p')
				setBoard(newPosition.col, newPosition.row, 'q');
		}//end if
		
		if(newPosition.row == 6){				//White Pawn reaches edge -- turns into new White Queen
			if(getBoardWithCoordinates(newPosition.col, newPosition.row) == 'P')
				setBoard(newPosition.col, newPosition.row, 'Q');
		}//end if
		
		//change the color of the Player -- next Players move
		changeColor();
		return outcome;
	}//end method
	
	/**
	 * MoveGenerator
	 * creates a move
	 * @return list of moves
	 */
	public ArrayList<Move> moveGen(){
		ArrayList<Move> moves = new ArrayList<Move>();
		ArrayList<Move> list = null;
		for (int i = 0; i < board.length; i++) {
			for (int j = 0; j < board[i].length; j++) {
				if(colorOf(j, i) == this.onMove){ 
					list = moveList(j, i, getBoardWithCoordinates(j, i));
					if(list != null)
						moves.addAll(list);
				}//end if
			}//end for j
		}//end for i
		
		if(moves.size() == 0)
			return null;
		
		return moves;
	}//End

	/**
	 * creates a move for a human player
	 * 
	 * @param coordinates
	 * @return
	 */
	public boolean humanMove(String coordinates){
		assert(coordinates.length() == 5);
		Move coord = new Move(coordinates);
		boolean illegalMove = false;
		
		Square oldPosition = coord.from;
		Square newPosition = coord.to;
		ArrayList<Move> playerMove = this.moveList(coord.to.col, coord.to.row, getBoardWithCoordinates(coord.from.col, coord.from.row));
		ArrayList<Move> moves = moveGen();
		
		for(int i = 0; i < moves.size(); i++){
			if(colorOf(coord.from.col, coord.from.row) == this.onMove){
				if(moves.equals(playerMove) == true){
					illegalMove = false;
				}else{
					illegalMove = true;
				}//end if else
			}//end if
		}//end for
		
		if(illegalMove == false){
			this.move(oldPosition, newPosition);
			
			return false;
		}else
			return true;
		
	}//end method
	
	/**
	 * creates a random move for a computer player
	 * now called from startGame's randomMove-Function
	 */
	public void randomMove(){
		ArrayList<Move> possibleMoves = moveGen();
		int r = ran.nextInt(possibleMoves.size());
		
		Move moveIt = possibleMoves.get(r);
		
//		if(moveIt != null)
			this.move(moveIt.from, moveIt.to);
		
	}//end method
	
	/**
	 * changes the color to the color of the player, whose turn it is
	 */
	public void changeColor(){
		if(this.onMove == 'b'){
			this.onMove = 'w';
			this.moveNum++;
		}
		else if(this.onMove == 'w')
			this.onMove = 'b';
	}
	
	/**
	 * corrects the board-coordinates
	 * @param x
	 * @param y
	 * @return
	 */
	public char getBoardWithCoordinates(int x, int y){
		return board[y][x];
	}//end
	
	/**
	 * sets the board-coordinates
	 * @param x
	 * @param y
	 * @param c
	 */
	public void setBoard(int x, int y, char c){
		board[y][x] = c;
	}//End
	
	/**
	 * returns a list of possible moves for the given piece
	 * 
	 * @param x
	 * @param y
	 * @param piece
	 * @return
	 */
	public ArrayList<Move> moveList(int x, int y, char piece){

		int dx = 0;
		int dy = 0;
		boolean stopShort = false;
		boolean capture = true;
		ArrayList<Move> moveList;
		ArrayList<Move> moves = new ArrayList<Move>();
		ArrayList<Move> m;
		
		switch (piece) {
		case 'q':
		case 'k':
		case 'Q':
		case 'K':

			for(dx = -1; dx <= 1; dx++){
				for(dy = -1; dy <= 1; dy++){
					if(dx == 0 && dy == 0){
						continue;
					}//end if
			
			if(piece == 'K' || piece == 'k')
					stopShort = true;
			
			moveList = moveScan(x, y, dx, dy, capture, stopShort);
			if(moveList.size() > 0)
				moves.addAll(moveList);
//			moves.addAll(moveScan(x, y, dx, dy, capture, stopShort));	
				}//end if
			}//end for	
			break;

		case 'r':
		case 'b':
		case 'R':
		case 'B':
			dx = 1;
			dy = 0; 
			
			if(piece == 'b' || piece == 'B'){
				stopShort = true;
				capture = false;
			}
			
//			if(piece == 'r' || piece == 'R')
//				capture = true;
			
			for(int i = 1; i <= 4; i++){
				moveList = moveScan(x, y, dx, dy, capture, stopShort);
				if(moveList.size() > 0)
					moves.addAll(moveList);
//				moves.addAll(moveScan(x, y, dx, dy, capture, stopShort));
				int help = dx;
				dx = dy;
				dy = help;
				dy = -dy;
			}//end for
			
			if(piece == 'b' || piece == 'B'){
				dx = 1;
				dy = 1;
				stopShort = false;
				capture = true;
				
				for(int i = 1; i <= 4; i++){
					moveList = moveScan(x, y, dx, dy, capture, stopShort);
					if(moveList.size() > 0)
						moves.addAll(moveList);
//					moves.addAll(moveScan(x, y, dx, dy, capture, stopShort));
					int help = dx;
					dx = dy;
					dy = help;
					dy = -dy;
				}//end for
			}//end if
			
			break;
		
		case 'n':
		case 'N':
			dx = 1;
			dy = 2;
			stopShort = true;
			
			for(int i = 1; i <= 4; i++){
				moveList = moveScan(x, y, dx, dy, capture, stopShort);
				if(moveList.size() > 0)
					moves.addAll(moveList);
//				moves.addAll(moveScan(x, y, dx, dy, capture, stopShort));
				int help = dx;
				dx = dy;
				dy = help;
				dy = -dy;
			}//end for
			
			dx = -1;
			dy = 2;
			stopShort = true;
			
			for(int i = 1; i <= 4; i++){
				moveList = moveScan(x, y, dx, dy, capture, stopShort);
				if(moveList.size() > 0)
					moves.addAll(moveList);
//				moves.addAll(moveScan(x, y, dx, dy, capture, stopShort));
				int help = dx;
				dx = dy;
				dy = help;
				dy = -dy;
			}//end for
			
			break;
			
		case 'p':
		case 'P':
			int dir = -1;
			char color = 'w';
			
			if(piece == 'p'){												//if piece is black
				dir = 1;
				color = 'b';
			}//end if
			
			stopShort = true;
			capture = true;
			
			m = moveScan(x, y, -1, dir, capture, stopShort);
			
			if(m.size() == 1){
				if(colorOf(m.get(0).to.col, m.get(0).to.row) != color && getBoardWithCoordinates(m.get(0).to.col, m.get(0).to.row) != '.'){		//if dest-piece is black and the moving one not
					moves.addAll(m);
				}//end if
			}//end if
				
			m = moveScan(x, y, 1, dir, capture, stopShort);
			
			if(m.size() == 1){
				if(colorOf(m.get(0).to.col, m.get(0).to.row) != color && getBoardWithCoordinates(m.get(0).to.col, m.get(0).to.row) != '.'){		//if dest-piece is white and the moving one not
					moves.addAll(m);
				}//end if
			}//end if

			capture = false;
			moveList = moveScan(x, y, 0, dir, capture, stopShort);
			if(moveList.size() > 0)
				moves.addAll(moveList);
//			moves.addAll(moveScan(x, y, 0, dir, capture, stopShort));
			
			break;
			
		default:
//			moves = null;
			return moves;
		}//end switch case
		return moves;
	}//end method
	
	/**
	 * tests, if the move is legal
	 * 
	 * @param x0
	 * @param y0
	 * @param dx
	 * @param dy
	 * @param capture
	 * @param stopShort
	 * @return
	 */
	public ArrayList<Move> moveScan(int x0, int y0, int dx, int dy, boolean capture, boolean stopShort){
			int x = x0;
			int y = y0;
			char color = colorOf(x, y);
			ArrayList<Move> moves = new ArrayList<Move>();

			do{
				x = x + dx;
				y = y + dy;
				
				if(x < 0 || x > 4)
					break;
				
				if(y < 0 || y > 5)
					break;
				
				if(getBoardWithCoordinates(x, y) != '.'){
					if(colorOf(x, y) == color)
						break;
					
					if(!capture)
						break;
					
					stopShort = true;
				}//end if
				
				Square from = new Square(x0, y0);
				Square to = new Square(x, y);
				Move m = new Move(from, to);
				moves.add(m);
				
			}while(!stopShort);				//mit oder ohne "!" ?
			//call move
			return moves;
	}//end method
	
	/**
	 * returns the color of a given piece	(needs coordinates)
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public char colorOf(int x, int y){
		if(Character.isLowerCase(getBoardWithCoordinates(x, y)))
			return 'b';
		else
			return 'w';
	}//end method
	
	public int negamax(int depth, boolean top) throws TimeOutException {
		int v = 0;
		int v2 = 0;

		if(State.limit < System.currentTimeMillis() - State.time)
			throw new TimeOutException();
		
		if(this.scoreSet() >= 10000 || depth <= 0){
			return this.scoreSet();
		}//end if
		
		ArrayList<Move> possibleMoves = moveGen();
		Move m = possibleMoves.get(this.ran.nextInt(possibleMoves.size()));

		State newS = new State(this);
		newS.move(m.from, m.to);
		
		v2 = -(newS.negamax(depth - 1, false));
		
		if(top)
			m0 = m;

		for(Move moves : possibleMoves){
			newS = new State(this);
			newS.move(moves.from, moves.to);
			v = -(newS.negamax(depth - 1, false));
		
			if(v > v2){
				v2 = v;
				
				if(top)
					m0 = moves;
			}//end if
		}//end for
		
		return v2;
	}//end method
	
	public int alphaBeta(int depth, int a, int b) throws TimeOutException {
		int v = -10000;
		
		if(State.limit < System.currentTimeMillis() - State.time)
			throw new TimeOutException();
		
		if(this.scoreSet() >= 10000 || depth <= 0){
			return this.scoreSet();
		}//end if
		
		ArrayList<Move> possibleMoves = moveGen();
		if(possibleMoves == null)
			throw new TimeOutException();
		
		for(Move moves : possibleMoves){
			State newS = new State(this);
			newS = new State(this);
			newS.move(moves.from, moves.to);
			v = Math.max(v, -(newS.alphaBeta(depth - 1, -b, -a)));
			a = Math.max(a, v);
			
			if(v >= b)
				return v;
		}
		
		return v;
	}//end method
	
	public String newBestMove(){
		int d = 1;
		String m0 = "resign";
		String m1 = null;
		
		try{
			while(true){
				int v = -10000;
				int a = -10000;
			
				ArrayList<Move> possibleMoves = moveGen();
			
				for(Move moves : possibleMoves){
					State newS = new State(this);
					newS.move(moves.from, moves.to);
					int v0 = Math.max(v, -alphaBeta(d, -10000, -a));
					a = Math.max(a, v0);

					if(v0 > v)
						m1 = m0;
				
					v = Math.max(v, v0);
				}
				d++;
			}
		}catch(TimeOutException e){
			return m1;
		}
	}//end method
	
	public void bestMove(){

		int d = 1;
		Move move = null;
		
		try{
			this.negamax(d, true);
			
//		while(this.limit - (System.currentTimeMillis() - this.time) > 0){
		while(true){
			this.negamax(d, true);
			move = this.m0;
			d++;
		}
//		this.m0 = move;
		}catch(TimeOutException e){
			this.m0 = move;
		}
		
	}//end method

	public int scoreSet(){
		int scoreBlack = 0;
		int scoreWhite = 0;

		for(int i=0; i < board.length; i++){
			for(int j=0; j < board[i].length; j++){
				
				char piece = getBoardWithCoordinates(j, i);
				
				if(piece == 'p')
					scoreBlack = scoreBlack + 100;
				if(piece == 'P')
					scoreWhite = scoreWhite + 100;
				if(piece == 'b' || piece == 'n')
					scoreBlack = scoreBlack + 300;
				if(piece == 'B' || piece == 'N')
					scoreWhite = scoreWhite + 300;
				if(piece == 'r')
					scoreBlack = scoreBlack + 500;
				if(piece == 'R')
					scoreWhite = scoreWhite + 500;
				if(piece == 'q')
					scoreBlack = scoreBlack + 900;
				if(piece == 'Q')
					scoreWhite = scoreWhite + 900;
				if(piece == 'k')
					scoreBlack = scoreBlack + 10000;
				if(piece == 'K')
					scoreWhite = scoreWhite + 10000;
				
			}//end for j
		}//end for i
		
		if(onMove == 'w')
			return scoreWhite - scoreBlack;
		else
			return scoreBlack - scoreWhite;
		
	}//end method
	
}//end class